#include <assert.h>
#include "ShadowMapFBO.hpp"
#include "../HDRDemoConstants.hpp"
#include "../HDRDemoUtils.hpp"
#include "../Globals.hpp"

#include "../SceneManager.hpp"

ShadowMapFBO::ShadowMapFBO()
{
	m_iShadowTexWidth  = TOTAL_SHADOW_WIDTH_4096;
	m_iShadowTexHeight = TOTAL_SHADOW_HEIGHT_4096;

	//! Set all things necessary to successfuly render shadow map
	createShadowMapFBO();
	createShadowTexture();
	initShadowMapShaders();
}

ShadowMapFBO::~ShadowMapFBO()
{
	destroyShadowMapFBO();
}

void ShadowMapFBO::renderToFBO(bool isTargetFBO)
{
	if( isTargetFBO )
	{
		//! Bind FBO - depth buffer will be rendered as a texture
		ExtensionsLoader::glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_frameBufferObject );
		ExtensionsLoader::glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_shadowMap, 0 );
		ExtensionsLoader::glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, m_renderBuffer );
		
		//glViewport( 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT );
		//glClearColor( 1.0f, 1.0f, 1.0f, 1.0f );

		//glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	
		
		//ExtensionsLoader::glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, m_frameBufferObject);
		//ExtensionsLoader::glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_shadowMap, 0);
		
		glPushAttrib (GL_VIEWPORT_BIT);
		glViewport(0, 0, m_iShadowTexWidth, m_iShadowTexHeight);

		glClearColor(1.0f, 1.0f, 1.0f, 1.0f);

		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	}
	else
	{
		//! Turn off FBO and render in standard mode
		glPopAttrib();
		ExtensionsLoader::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
		//glClearColor( 0.3f, 0.3f, 0.3f, 0.3f );
		//glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	}
}

bool ShadowMapFBO::initShadowMapShaders()
{
	//TODO: set uniform variables
	bool bResult = true;

	std::string vertCode, fragCode;

	char *pVertCodeChartable, *pFragCodeChartable;
	pVertCodeChartable = pFragCodeChartable = NULL;


	//! ======= DEPTH MAP GENERATOR================================================================================
	//! Read code, create shader, compile, create and link program for depth map generator.
	m_depthMapGenVert = ExtensionsLoader::glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
	m_depthMapGenFrag = ExtensionsLoader::glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);

	HDRDemoUtils::readTextFile(&vertCode, DEPTH_MAP_GEN_VERTEX_PROGRAM_SRC);
	HDRDemoUtils::readTextFile(&fragCode, DEPTH_MAP_GEN_FRAGMENT_PROGRAM_SRC);

	pVertCodeChartable = new char[vertCode.size()+1];
	pVertCodeChartable[vertCode.size()] = NULL_TERMINATOR;
	vertCode.copy(pVertCodeChartable, vertCode.size());
	pFragCodeChartable = new char[fragCode.size()+1];
	pFragCodeChartable[fragCode.size()] = NULL_TERMINATOR;
	fragCode.copy(pFragCodeChartable, fragCode.size());

	ExtensionsLoader::glShaderSourceARB(m_depthMapGenVert, 1, const_cast<const char**>(&pVertCodeChartable), NULL);
	ExtensionsLoader::glShaderSourceARB(m_depthMapGenFrag, 1, const_cast<const char**>(&pFragCodeChartable), NULL);

	delete[] pVertCodeChartable; pVertCodeChartable = NULL;
	delete[] pFragCodeChartable; pFragCodeChartable = NULL;
	vertCode.clear();
	fragCode.clear();

	ExtensionsLoader::glCompileShaderARB(m_depthMapGenVert);
	ExtensionsLoader::glCompileShaderARB(m_depthMapGenFrag);
	m_depthMapProgram = ExtensionsLoader::glCreateProgramObjectARB();
	ExtensionsLoader::glAttachObjectARB(m_depthMapProgram, m_depthMapGenVert);
	ExtensionsLoader::glAttachObjectARB(m_depthMapProgram, m_depthMapGenFrag);
	ExtensionsLoader::glLinkProgramARB(m_depthMapProgram);
	//! ===== END OF DEPTH MAP GENERATOR ================================================================================


	//! ====== SHADOW MAP RENDERER ======================================================================================
	//! Read code, create shader, compile, create and link program for shadowed scene renderer.
	m_shadowRenderVert = ExtensionsLoader::glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
	m_shadowRenderFrag = ExtensionsLoader::glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);

	HDRDemoUtils::readTextFile(&vertCode, DEPTH_MAP_REND_VERTEX_PROGRAM_SRC);
	HDRDemoUtils::readTextFile(&fragCode, DEPTH_MAP_REND_FRAGMENT_PROGRAM_SRC);

	pVertCodeChartable = new char[vertCode.size()+1];
	pVertCodeChartable[vertCode.size()] = NULL_TERMINATOR;
	vertCode.copy(pVertCodeChartable, vertCode.size());
	pFragCodeChartable = new char[fragCode.size()+1];
	pFragCodeChartable[fragCode.size()] = NULL_TERMINATOR;
	fragCode.copy(pFragCodeChartable, fragCode.size());

	ExtensionsLoader::glShaderSourceARB(m_shadowRenderVert, 1, const_cast<const char**>(&pVertCodeChartable), NULL);
	ExtensionsLoader::glShaderSourceARB(m_shadowRenderFrag, 1, const_cast<const char**>(&pFragCodeChartable), NULL);

	delete[] pVertCodeChartable; pVertCodeChartable = NULL;
	delete[] pFragCodeChartable; pFragCodeChartable = NULL;
	vertCode.clear();
	fragCode.clear();

	ExtensionsLoader::glCompileShaderARB(m_shadowRenderVert);
	ExtensionsLoader::glCompileShaderARB(m_shadowRenderFrag);
	m_shadowRenderProgram = ExtensionsLoader::glCreateProgramObjectARB();
	ExtensionsLoader::glAttachObjectARB(m_shadowRenderProgram, m_shadowRenderVert);
	ExtensionsLoader::glAttachObjectARB(m_shadowRenderProgram, m_shadowRenderFrag);
	ExtensionsLoader::glLinkProgramARB(m_shadowRenderProgram);
	//! ====== END OF SHADOW MAP RENDERER ===================================================================================

	return bResult;
}

bool ShadowMapFBO::createShadowTexture()
{
	//TODO: after calling glTexImage2D() we should check if texture have been created successfully.

	bool bResult = true;

	float* pShadowTexture = NULL;
	int iCellCount = m_iShadowTexHeight * m_iShadowTexWidth * RGB_CHANNELS;

	//! Fill structure data with zeros. In case of depth map 0 values means that this point have smallest possible distance from camera.
	pShadowTexture = new float[iCellCount];
	for(int i = 0; i < iCellCount; ++i)
		pShadowTexture[i] = 0.0f;

	//! Generate texture with apropriate dimmensions and format settings.
	glGenTextures(1, &m_shadowMap);
	glBindTexture(GL_TEXTURE_2D, m_shadowMap);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16, m_iShadowTexWidth, m_iShadowTexHeight, 0, GL_RGB, GL_FLOAT, pShadowTexture);

	//! Set scalling type and wrapping handling.
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

	return bResult;
}

bool ShadowMapFBO::createShadowMapFBO()
{
	bool bResult = true;
	////! Generate framebuffer and renderbuffer
	//ExtensionsLoader::glGenFramebuffersEXT( 1, &m_frameBufferObject );
	//ExtensionsLoader::glGenRenderbuffersEXT( 1, &m_depthBuffer );
	//
	////! Bind renderbuffer and set its storage type
	//ExtensionsLoader::glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, m_depthBuffer );
	//ExtensionsLoader::glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, WINDOW_WIDTH, WINDOW_HEIGHT );

	////! Check if everything is OK with frame buffer
	//if( !IsShadowMapFBOOK() )
	//	return false;

	////! Generate and bind texture used as shadow map
	//glGenTextures( 1, &m_shadowMap );
	//glBindTexture( GL_TEXTURE_2D, m_shadowMap );

	////! Create texture in VRAM
	//glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 
	//	          WINDOW_WIDTH, WINDOW_HEIGHT, 
	//	          0, GL_RGB, GL_FLOAT, 0 );

	////! Set texture parameters - nearest filtering used due to errors in shadow mapping while using GL_LINEAR
	//glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	//glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

//! TEST
	ExtensionsLoader::glGenFramebuffersEXT(1, &m_frameBufferObject);
	ExtensionsLoader::glGenRenderbuffersEXT(1, &m_renderBuffer);

	ExtensionsLoader::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, m_renderBuffer);
	ExtensionsLoader::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, m_iShadowTexWidth, m_iShadowTexHeight);
	
	bResult = isShadowMapFBOOK();

	return bResult;
}

bool ShadowMapFBO::destroyShadowMapFBO()
{
	bool bResult = true;

	//! Delete framebuffer and renderbuffer
	ExtensionsLoader::glDeleteFramebuffersEXT( 1, &m_frameBufferObject );
	ExtensionsLoader::glDeleteRenderbuffersEXT( 1, &m_depthBuffer );

	//! Delete shadow map texture from VRAM
	glDeleteTextures( 1, &m_shadowMap );

	return bResult;
}
bool ShadowMapFBO::isShadowMapFBOOK() const
{
	bool bResult = true;

	// Check for errors
    GLenum status;                                            
    status = ExtensionsLoader::glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT ); 
    switch( status ) 
	{                                          
      case GL_FRAMEBUFFER_COMPLETE_EXT:                       
		  bResult = true;
        break;                                                
      case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
		  bResult = false;
        /* choose different formats */                        
        break;                                                
      default:                                                
        /* programming error; will fail on all hardware */    
        //assert( 0 );                                            
		  bResult = false;
    }

	return bResult;
}

GLuint ShadowMapFBO::getShadowMapID() const
{
	return m_shadowMap;
}

bool ShadowMapFBO::computeShadowMap(int iXSubmapsNum, int iYSubmapsNum)
{
	//TODO: Wrong numbers of submaps must be handled in order to ensure that submap width and height are powers of 2.

	bool bResult = true;
	int iSubmapWidth, iSubmapHeight;

	iSubmapWidth = m_iShadowTexWidth / iXSubmapsNum;
	iSubmapHeight = m_iShadowTexHeight / iYSubmapsNum;
	
	renderToFBO(true);

	//glBindTexture( GL_TEXTURE_2D, *(Globals::GetInstance()->GetSpecialTexture( LIGHT_POSITIONS )) );

	//glEnable( GL_POLYGON_OFFSET_FILL );
	//glPolygonOffset(0.5, 1.0);

	ExtensionsLoader::GetInstance()->glUseProgramObjectARB( ExtensionsLoader::GetInstance()->GetShader( DEPTH_MAP_GENERATOR ) );

	mvMatricesVec mvec = Globals::GetInstance()->GetLookAtMaticesVector();
	mvMatricesVec::iterator mat_it = mvec.begin();
	int size = mvec.size();

	//glMatrixMode( GL_MODELVIEW );
	int shadcount=0;
	MMatrix4 matrix;

	//glCullFace(GL_FRONT);
	//glEnable(GL_CULL_FACE);
	for( int i = 0; i < m_iShadowTexWidth; i+=iSubmapWidth )
	{
		for( int j = 0; j < m_iShadowTexHeight; j+=iSubmapHeight )
		{
			matrix = (*mat_it)->GetModelViewMatrix();
			Globals::GetInstance()->SetLightMatrix( MODELVIEW_MATRIX, matrix );
			glLoadMatrixd( Globals::GetInstance()->GetLightMatrix( MODELVIEW_MATRIX) );
			++shadcount;
			glViewport(i,j, iSubmapWidth, iSubmapHeight);

			SceneManager::GetInstance()->drawEntities();

			++mat_it;
		}
	}
	//cout << "shadcount: " << shadcount << endl;
	//glDisable(GL_CULL_FACE);
	//glDisable(GL_POLYGON_OFFSET_FILL);

	renderToFBO(false);

	return bResult;
}

//EOF
